<!DOCTYPE html>

Global Shala Campaign Analysis

Overview of Campaigns

The dataset originates from eleven Facebook advertising campaigns conducted by Global Shala. These campaigns predominantly utilized basic image-based advertisements, commonly referred to as “Link Click” ads. The campaign strategy targeted two distinct audience segments: students and educators/principals from countries including; Ghana, Australia, US, UK, UAE, Canada, Nigeria, India & Nepal. This analysis focuses on evaluating the performance of these campaigns, with the objective of identifying and recommending one underperforming campaign for discontinuation. Dataset includes columns such as;

  1. Campaign Name: the name of the campaign.
  2. Audience: the target of the ad campaign
  3. Age: the age of the targeted audience. These are divided into groups.
  4. Reach: the number of unique users who saw the campaign
  5. Impressions: total number of times the campaign was displayed
  6. Clicks and Unique Clicks: measures of user engagement
  7. Unique Link Clicks (ULC): the number of unique users who clicked on a link in the ad. Unlike total clicks, which can include multiple clicks from the same user, ULC counts only one link click per user, which makes this a more accurate measure of how many individuals were genuinely interested.
  8. Click-Through Rate (CTR): percentage of impressions that resulted in clicks
  9. Unique Click-Through Rate (UCTR): The percentage of unique users who saw the ad and clicked on the link. This tells us how effective the campaign is at engaging individual viewers. It removes the bias caused by repeat clicks from the same person.
  10. Frequency: average number of times a user saw the ad
  11. Amount Spent: total cost incurred for the campaign
  12. Cost Per Click (CPC) and Cost Per Result (CPR): efficiency metrics indicating how much was spent per user interaction or conversion

Based on these KPIs, we will compare the performance of each of these campaigns through EDA and visualizations. We have come up with the following criteria for recommending the removal of a campaign:

  1. Low CTR: Poor engagement
  2. High CPC/CPR: poor cost efficiency
  3. Low overall reach or impression volume: limited visibility
  4. High budget usage with minimal returns and clicks: inefficiency

Data Cleaning and Preparation

Load Packages & Functions

To efficiently clean, organize, and visualize datasets in R, it is essential to load the appropriate libraries and define custom functions that streamline the workflow and enhance productivity.

library(tidyverse)
library(readxl)
library(knitr)
library(scales)
library(kableExtra)
library(rmarkdown)
library(patchwork)
nicelimits <- function(x) {
  range(scales::extended_breaks(only.loose = TRUE)(x))
}
highlight = function(x, pat, color="black", family="") {
  ifelse(grepl(pat, x), glue("<b style='font-family:{family}; color:{color}'>{x}</b>"), x)
}

Data Cleaning

To begin the analysis, the raw dataset was first imported from a CSV file. The original column headers were lengthy and would have cluttered visual outputs and tables. As a result, the headers were systematically renamed to more concise, readable forms to ensure clarity throughout the report.

For transparency and ease of interpretation, a table footnote was included to explain all abbreviations used in the renamed columns.

Following the initial cleaning, key performance metrics such as Cost Per Click (CPC), Click-Through Rate (CTR), and Unique Link Clicks (ULC) were calculated or validated to support further analysis. These metrics formed the foundation for subsequent visualizations and campaign evaluations.

# Raw dataset
campaign_raw <- read_excel("EXCELERATE_main.xlsx", sheet = "marketing_team_data", range = "A1:P34")
 
campaign_raw <- campaign_raw |>
  mutate(Geography = recode(Geography, "Group 1 (Australia, Canada, United Kingdom, Ghana, Nigeria, Pakistan, United States)"="Group 1",
                            "Group 2 (Australia, Canada, United Kingdom, Ghana, Niger, Nigeria, Nepal, Pakistan, Thailand, Taiwan)"="Group 2"))|>
  rename(campaign_id=`campaign ID`, 
         campaign_name=`Campaign Name`, 
         ULC = `Unique Link Clicks (ULC)`, 
         CTR = `Click-Through Rate (CTR in %)`, 
         uCTR = `Unique Click-Through Rate (Unique CTR in %)`,
         amount_spent = `Amount Spent in INR`,
         CPC = `Cost Per Click (CPC)`,
         CPR = `Cost per Result (CPR)`)
campaign_raw |>
  kable(caption = "Raw Marketting Campaign Dataset") |> 
  kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = TRUE) |>
  scroll_box(width = "500", height = "100") |>
  footnote(general = "ULC = Unique Link Clicks (ULC),
         CTR = Click-Through Rate (CTR in %),
         uCTR = Unique Click-Through Rate (Unique CTR in %),
         amount_spent = Amount Spent in $,
         CPC = Cost Per Click (CPC),
         CPR = Cost per Result (CPR))
         Group 1 includes Australia, Canada, United Kingdom, Ghana, Nigeria, Pakistan, United States.
         Group 2 includes Australia, Canada, United Kingdom, Ghana, Niger, Nigeria, Nepal, Pakistan, Thailand, Taiwan.")
Raw Marketting Campaign Dataset
campaign_id campaign_name Audience Age Geography Reach Impressions Frequency Clicks Unique Clicks ULC CTR uCTR amount_spent CPC CPR
Campaign 1 SHU_6 (Educators and Principals) Educators and Principals 25-34 Group 1 11387 23283 2.044700 487 406 180 2.09 3.57 1092.24 2.24 6.07
Campaign 1 SHU_6 (Educators and Principals) Educators and Principals 35-44 Group 1 8761 15683 1.790093 484 376 154 3.09 4.29 835.46 1.73 5.43
Campaign 1 SHU_6 (Educators and Principals) Educators and Principals 45-54 Group 1 2867 6283 2.191489 198 145 65 3.15 5.06 319.38 1.61 4.91
Campaign 1 SHU_6 (Educators and Principals) Educators and Principals 55-64 Group 1 889 1890 2.125984 49 40 21 2.59 4.50 86.25 1.76 4.11
Campaign 2 SHU3_ (Students Apart from India and US) Students 18-24 Group 2 29675 39161 1.319663 2593 1994 1095 6.62 6.72 1193.94 0.46 1.09
Campaign 2 SHU3_ (Students Apart from India and US) Students 13-17 Group 2 14753 25705 1.742358 969 698 435 3.77 4.73 299.51 0.31 0.69
Campaign 2 SHU3_ (Students Apart from India and US) Students 25-34 Group 2 2066 2447 1.184414 181 141 65 7.40 6.82 85.57 0.47 1.32
Campaign 3 SHU_Students(Australia) Students 13-17 Australia 2271 2616 1.151916 61 55 28 2.33 2.42 475.85 7.80 16.99
Campaign 3 SHU_Students(Australia) Students 18-24 Australia 704 734 1.042614 49 46 13 6.68 6.53 283.17 5.78 21.78
Campaign 3 SHU_Students(Australia) Students 25-34 Australia 212 222 1.047170 9 8 3 4.05 3.77 91.66 10.18 30.55
Campaign 4 SHU_Students (Canada) Students 13-17 Canada 2330 3146 1.350215 101 84 63 3.21 3.61 528.08 5.23 8.38
Campaign 4 SHU_Students (Canada) Students 18-24 Canada 759 878 1.156785 52 44 34 5.92 5.80 294.82 5.67 8.67
Campaign 4 SHU_Students (Canada) Students 25-34 Canada 218 243 1.114679 18 18 15 7.41 8.26 101.06 5.61 6.74
Campaign 5 SHU_Students(Ghana) Students 18-24 Ghana 5952 6943 1.166499 284 238 98 4.09 4.00 378.10 1.33 3.86
Campaign 5 SHU_Students(Ghana) Students 25-34 Ghana 3717 4620 1.242938 184 160 46 3.98 4.30 282.22 1.53 6.14
Campaign 5 SHU_Students(Ghana) Students 13-17 Ghana 5355 8920 1.665733 180 154 93 2.02 2.88 177.46 0.99 1.91
Campaign 6 SHU_Students (India) Students 18-24 India 30110 35372 1.174759 1308 1162 934 3.70 3.86 894.00 0.68 0.96
Campaign 6 SHU_Students (India) Students 25-34 India 1721 1874 1.088902 92 76 53 4.91 4.42 61.21 0.67 1.15
Campaign 7 SHU_Students(Nepal) Students 18-24 Nepal 18900 36659 1.939630 849 688 306 2.32 3.64 634.64 0.75 2.07
Campaign 7 SHU_Students(Nepal) Students 13-17 Nepal 6145 19474 3.169081 325 246 129 1.67 4.00 211.76 0.65 1.64
Campaign 7 SHU_Students(Nepal) Students 25-34 Nepal 4623 9082 1.964525 246 212 83 2.71 4.59 188.84 0.77 2.28
Campaign 8 SHU_Students (Nigeria) Students 18-24 Nigeria 11027 13820 1.253287 1491 1132 548 10.79 10.27 542.67 0.36 0.99
Campaign 8 SHU_Students (Nigeria) Students 13-17 Nigeria 8516 12372 1.452795 970 696 408 7.84 8.17 282.21 0.29 0.69
Campaign 8 SHU_Students (Nigeria) Students 25-34 Nigeria 2386 2782 1.165968 304 230 117 10.93 9.64 117.90 0.39 1.01
Campaign 9 SHU_Students(UAE) Students 25-34 UAE 2892 3347 1.157331 135 102 41 4.03 3.53 455.49 3.37 11.11
Campaign 9 SHU_Students(UAE) Students 18-24 UAE 2862 3234 1.129979 72 60 27 2.23 2.10 316.14 4.39 11.71
Campaign 9 SHU_Students(UAE) Students 13-17 UAE 1579 2079 1.316656 35 32 20 1.68 2.03 104.63 2.99 5.23
Campaign 10 SHU_Students(UK) Students 13-17 UK 2557 2941 1.150176 69 60 33 2.35 2.35 487.52 7.07 14.77
Campaign 10 SHU_Students(UK) Students 18-24 UK 741 785 1.059379 39 34 20 4.97 4.59 255.57 6.55 12.78
Campaign 10 SHU_Students(UK) Students 25-34 UK 338 365 1.079882 13 11 4 3.56 3.25 113.58 8.74 28.40
Campaign 11 SHU_Students (USA) Students 13-17 USA 2159 2465 1.141732 126 111 95 5.11 5.14 691.28 5.49 7.28
Campaign 11 SHU_Students (USA) Students 18-24 USA 305 332 1.088525 43 37 28 12.95 12.13 159.14 3.70 5.68
Campaign 11 SHU_Students (USA) Students 25-34 USA 91 103 1.131868 9 8 3 8.74 8.79 47.26 5.25 15.75
Note:
ULC = Unique Link Clicks (ULC),
CTR = Click-Through Rate (CTR in %),
uCTR = Unique Click-Through Rate (Unique CTR in %),
amount_spent = Amount Spent in $,
CPC = Cost Per Click (CPC),
CPR = Cost per Result (CPR))
Group 1 includes Australia, Canada, United Kingdom, Ghana, Nigeria, Pakistan, United States.
Group 2 includes Australia, Canada, United Kingdom, Ghana, Niger, Nigeria, Nepal, Pakistan, Thailand, Taiwan.
# Cleaning dataset  
campaign_clean <- campaign_raw|>
  group_by(campaign_id,campaign_name,Audience,Geography)|>
  summarise(across(where(is.numeric), sum, na.rm = TRUE))|>
  mutate(Frequency = Impressions/Reach,
         CTR = Clicks/Impressions*100,
         uCTR = `Unique Clicks`/Reach*100,
         CPC= amount_spent/Clicks,
         CPR= amount_spent/ULC,
         Geography = recode(Geography, "Group 1 (Australia, Canada, United Kingdom, Ghana, Nigeria, Pakistan, United States)"="Group 1",
                            "Group 2 (Australia, Canada, United Kingdom, Ghana, Niger, Nigeria, Nepal, Pakistan, Thailand, Taiwan)"="Group 2"))

campaign_clean |>
  kable(caption = "Cleaned Campaign Dataset") |> 
  kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = TRUE) |>
  scroll_box(width = "500", height = "200") |>
  footnote(general = "ULC = Unique Link Clicks (ULC),
  CTR = Click-Through Rate (CTR in %),
  uCTR = Unique Click-Through Rate (Unique CTR in %),
  amount_spent = Amount Spent in $,
  CPC = Cost Per Click (CPC),
  CPR = Cost per Result (CPR))
  Group 1 includes Australia, Canada, United Kingdom, Ghana, Nigeria, Pakistan, United States.
  Group 2 includes Australia, Canada, United Kingdom, Ghana, Niger, Nigeria, Nepal, Pakistan, Thailand, Taiwan.")
Cleaned Campaign Dataset
campaign_id campaign_name Audience Geography Reach Impressions Frequency Clicks Unique Clicks ULC CTR uCTR amount_spent CPC CPR
Campaign 1 SHU_6 (Educators and Principals) Educators and Principals Group 1 23904 47139 1.972013 1218 967 420 2.583848 4.045348 2333.33 1.9157061 5.5555476
Campaign 10 SHU_Students(UK) Students UK 3636 4091 1.125137 121 105 57 2.957712 2.887789 856.67 7.0799174 15.0292982
Campaign 11 SHU_Students (USA) Students USA 2555 2900 1.135029 178 156 126 6.137931 6.105675 897.68 5.0431461 7.1244444
Campaign 2 SHU3_ (Students Apart from India and US) Students Group 2 46494 67313 1.447778 3743 2833 1595 5.560590 6.093259 1579.02 0.4218595 0.9899812
Campaign 3 SHU_Students(Australia) Students Australia 3187 3572 1.120803 119 109 44 3.331467 3.420144 850.68 7.1485714 19.3336364
Campaign 4 SHU_Students (Canada) Students Canada 3307 4267 1.290293 171 146 112 4.007499 4.414878 923.96 5.4032749 8.2496429
Campaign 5 SHU_Students(Ghana) Students Ghana 15024 20483 1.363352 648 552 237 3.163599 3.674121 837.78 1.2928704 3.5349367
Campaign 6 SHU_Students (India) Students India 31831 37246 1.170117 1400 1238 987 3.758793 3.889290 955.21 0.6822929 0.9677913
Campaign 7 SHU_Students(Nepal) Students Nepal 29668 65215 2.198160 1420 1146 518 2.177413 3.862748 1035.24 0.7290423 1.9985328
Campaign 8 SHU_Students (Nigeria) Students Nigeria 21929 28974 1.321264 2765 2058 1073 9.543039 9.384833 942.78 0.3409693 0.8786393
Campaign 9 SHU_Students(UAE) Students UAE 7333 8660 1.180963 242 194 88 2.794457 2.645575 876.26 3.6209091 9.9575000
Note:
ULC = Unique Link Clicks (ULC),
CTR = Click-Through Rate (CTR in %),
uCTR = Unique Click-Through Rate (Unique CTR in %),
amount_spent = Amount Spent in \(,<br> CPC = Cost Per Click (CPC),<br> CPR = Cost per Result (CPR))<br> Group 1 includes Australia, Canada, United Kingdom, Ghana, Nigeria, Pakistan, United States.<br> Group 2 includes Australia, Canada, United Kingdom, Ghana, Niger, Nigeria, Nepal, Pakistan, Thailand, Taiwan. </td> </tr> </tfoot> </table> </div> </div> <div id="initial-analysis" class="section level2"> <h2>Initial Analysis</h2> <p>Performing a preliminary analysis of the dataset first, to discover trends and relationships, and familiarize ourselves with the dataset using R. <br></p> <div id="bar-charts" class="section level3"> <h3>Bar Charts</h3> <p>Looking at our dataset,specific ad campaigns are targeted towards specific audience. Let’s analyse individual campaigns to see how they are peforming.</p> <div class="sourceCode" id="cb4"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb4-1"><a href="#cb4-1" tabindex="-1"></a>amountspent_campaign <span class="ot">&lt;-</span> campaign_clean</span> <span id="cb4-2"><a href="#cb4-2" tabindex="-1"></a></span> <span id="cb4-3"><a href="#cb4-3" tabindex="-1"></a>amountspent_campaign <span class="sc">|&gt;</span></span> <span id="cb4-4"><a href="#cb4-4" tabindex="-1"></a> <span class="fu">ggplot</span>(<span class="fu">aes</span>(<span class="at">x =</span> <span class="fu">reorder</span>(campaign_id,<span class="sc">-</span>amount_spent), <span class="at">y =</span> amount_spent))<span class="sc">+</span></span> <span id="cb4-5"><a href="#cb4-5" tabindex="-1"></a> <span class="fu">geom_col</span>(<span class="at">width =</span> <span class="fl">0.9</span>, <span class="at">fill =</span> <span class="st">&quot;#09979B&quot;</span>)<span class="sc">+</span></span> <span id="cb4-6"><a href="#cb4-6" tabindex="-1"></a> <span class="fu">geom_text</span>(<span class="fu">aes</span>(<span class="at">label =</span> <span class="fu">paste0</span>(<span class="st">&quot;\)",scales::comma(amount_spent,accuracy = 0.01))), family = "Century Gothic", fontface = "bold", vjust = -0.5, size = 5)+ scale_y_continuous(expand = c(0,1), limits = nicelimits)+ scale_x_discrete(label = scales::wrap_format(width = 10))+ labs(x = NULL, y = "Amount Spent", title = "Amount Spent on Campaigns")+ theme(text = element_text(family = "Century Gothic", face = "bold", size = 14, color = "black"), panel.background = element_blank(), panel.grid.major.y = element_line(colour = "gray",size = 0.25), axis.text = element_text(size = 11, colour = "black"), plot.title = element_text(hjust = 0.5, size = 15))

From the above chart chart, we can determine that the Campaign 1 (Educators and Principals) campaign has been the most expensive, whereas the campaign targeting the students of Ghana (Campaign 5) has been the least expensive. But if we see a comparison of Campaign with Reach, we notice something peculiar:

reach_campaign <- campaign_clean

reach_campaign |>
  ggplot(aes(x = reorder(campaign_id,-Reach), y = Reach))+
  geom_col(width = 0.9, fill = "#27A0CC")+
  geom_text(aes(label = scales::comma(Reach), vjust = -0.5), family = "Century Gothic", fontface = "bold", size = 5)+
  scale_y_continuous(expand = c(0,0), limits = nicelimits)+
  scale_x_discrete(label = scales::wrap_format(width = 10))+
  labs(x=NULL, title = "Reach Across Campaigns")+
  theme(text = element_text(family = "Century Gothic", face = "bold", size = 14, color = "black"),
        panel.background = element_blank(),
        panel.grid.major.y = element_line(colour = "gray",size = 0.25),
        axis.text = element_text(size = 11, colour = "black"),
        plot.title = element_text(hjust = 0.5, size = 15))

Campaign 1, The Educators and Principals campaign is not the campaign with the highest reach. It isn’t even the second-highest reach – it comes at the fourth position. Whereas the inexpensive Campaing 5 , The Ghana campaign is in the 6th position, showing more reach with low cost. Let’s explore this further;

geo_cpc <- campaign_clean

geo_cpc |>
  ggplot(aes(x=reorder(Geography,CPC), y=CPC, fill = CPC))+
  geom_col(width = 0.8)+
  scale_fill_gradient2(high = "#2ECC71",mid = "#F1C40F", low = "#E74C3C", midpoint = median(campaign_clean$CPC,na.rm = TRUE))+
  coord_flip(clip = "off")+
  labs(x=NULL, y = "Cost Per Click", title = "Cost Per Click Across Geographic locations")+
  geom_text(aes(label = paste0(round(CPC,2),"$")), hjust =-0.1, family = "Century Gothic", size = 5, fontface="bold")+
  scale_y_continuous(expand = c(0,0),limits = nicelimits)+
  theme(text = element_text(family = "Century Gothic", size = 15, face = "bold", colour = "black"),
    axis.text = element_text(family = "Century Gothic", size = 15, face = "bold", colour = "black"),
    panel.background = element_blank(),
    panel.grid.major.x = element_line(colour = "gray",size = 0.25),
    legend.position = "none",
    plot.title = element_text(hjust = 0.5, size = 15))

Nigeria has some of the lowest CPC, but India, which had a more successful ad campaign in terms of reach, also has a low CPC.

Scatter/Bubble plots

A bubble plot to explore the relationship between Reach and Impressions

reach_imp <- campaign_clean


reach_imp|>
  ggplot(aes(x=Reach, y = Impressions, size = ULC, colour = campaign_id))+
  geom_point(alpha = 0.7, show.legend = c(colour = FALSE))+
  geom_text(aes(label = if_else(Reach > 5000, str_wrap(campaign_id,width = 15),"")),
            size = 4.5, colour = "black", vjust = -2.5,family = "Century Gothic", fontface = "bold")+
  scale_size_continuous(range = c(4,20))+
  scale_x_continuous(limits = nicelimits)+
  scale_y_continuous(limits = c(0,80000)) +
  labs(x = "Reach (Unique Users)", y = "Impressions (Total Views) ", size = "Unique Link Clicks", 
       title = "Reach vs. Impressions \n(Bubble Size = Unique Link Clicks)")+
  scale_colour_manual(values = c("Campaign 1"="#4B644B",
                                 "Campaign 2"="#7D96AF",
                                 "Campaign 3"="#F98B00",
                                 "Campaign 4"="#09979B",
                                 "Campaign 5"="#EA879C",
                                 "Campaign 6"="#FDD835",
                                 "Campaign 7"="#0070C0",
                                 "Campaign 8"="#AE2B47",
                                 "Campaign 9"="#EBA07E",
                                 "Campaign 10"="#E06666",
                                 "Campaign 11"="#002060"))+
  theme(text = element_text(family = "Century Gothic", face = "bold",size = 14),
        panel.background = element_blank(),
        axis.line = element_line(colour = "black", size = 1),
        axis.text = element_text(size = 15, colour = "black"),
        legend.text = element_text(size = 14, colour = "black"),
        plot.title = element_text(hjust = 0.5))

On the X-axis, we have Reach, which shows the number of unique users who saw each campaign. On the Y-axis, we have Impressions, which is the total number of times the ad was shown. Unique Link Clicks (ULC) was used for the bubble size, which shows how many individuals clicked on the ad. And, finally, each color represents a different campaign. Top-right corner means the best visibility. Campaigns in the top-right have high reach and high impressions, which implies strong visibility. We find that the Campaign 2 (SHU_3 (Students Apart from India and US)) campaign and Campaign 7 (SHU_ Students (Nepal)) both had high reach and impressions, making them stand out. Let’s analyze bubble size, which translates to engagement: larger bubbles mean more people clicked on the ad.

The Campaign 2 (SHU3 campaign) has a large bubble, suggesting high engagement. Campaigns 4 and 11 (Canada and USA) have tiny bubbles, indicating low click activity, even with modest reach. Campaigns positioned in the lower-left corner of the graph represent poor performers. These campaigns have low reach, low impressions, and small bubble sizes. Some of these campaigns are Campaign 9 (SHU Students (UAE)), Campaign 11 (SHU Students (USA)), and Campaign 4 (SHU Students (Canada)), reflecting low visibility and weak engagement. Campaign 10, the UK campaign is so insignificant in terms of Reach that it cannot even be displayed.

Heatmaps

Visualizing how reach varies across campaigns and Age Range using a heatmap.

campaign_age <- campaign_raw         

campaign_age <- campaign_age |>
  select(Age, campaign_id, Reach)
  
  
heatmap <- campaign_age |>
  pivot_wider(id_cols = c(campaign_id),
              names_from = "Age",
              values_from = "Reach") |>
  mutate(All_Ages= rowSums(across(c("13-17","18-24","25-34","35-44","45-54","55-64")), na.rm = TRUE))

heatmap <- heatmap |>
  add_row(campaign_id="All Campaigns",`13-17`=sum(heatmap$`13-17`, na.rm = TRUE),`18-24`=sum(heatmap$`18-24`, na.rm = TRUE), `25-34`= sum(heatmap$`25-34`, na.rm = TRUE),
          `35-44`=sum(heatmap$`35-44`, na.rm = TRUE), `45-54`=sum(heatmap$`45-54`, na.rm = TRUE), `55-64`=sum(heatmap$`55-64`, na.rm = TRUE), All_Ages=sum(heatmap$All_Ages, na.rm = TRUE))


heatmap_long <- heatmap |>
  pivot_longer(-c(`campaign_id`),
               names_to = "age",
               values_to = "number")

# Replace NA values in dataframe with 0
heatmap_long[is.na(heatmap_long)] <- 0

heatmap_long |>
  mutate(campaign_id = factor(campaign_id, levels = rev(c("All Campaigns","Campaign 2","Campaign 6","Campaign 7","Campaign 1","Campaign 8",
                                                          "Campaign 5","Campaign 9","Campaign 10","Campaign 4","Campaign 3","Campaign 11"))))|>
  ggplot(aes(x = age, y = campaign_id, fill = ifelse(campaign_id == "All Campaigns" | age == "All_Ages", NA, number)))+
  geom_tile()+
  geom_text(aes(label = scales::comma(number)), family = "Century Gothic", size = 4.5)+
  scale_fill_gradientn(colours = c("#FFFFCC","#C7E9B4","#7FCDBB","#41B6C4","#2C7FB8"), na.value = "#F2F1F1")+
  labs(fill = "Reach", y = NULL, x = NULL, title = "Reach Across Campaigns by Age Group")+
  scale_y_discrete(expand = c(0,0))+
  scale_x_discrete(expand = c(0,0))+
  theme(
    text = element_text(family = "Century Gothic", face = "bold"),
    axis.text = element_text(size = 13, colour = "black"),
    axis.text.y = element_text(hjust = 1),
    panel.grid.major = element_blank(),
    plot.title = element_text(hjust = 0.5, size = 15))

According to this heatmap, across most campaigns, 18–24 is the strongest performing age group. This is most prominent in Campaign 6, SHU_Students (India) campaign, where we see a 30,110 reach. The second-strongest is Campaign 2 (SHU3_ Students Apart from India and US)). But the older age groups, ranging from 35 all the way to 64, show lighter shades, which implies that campaigns are less effective among older audiences. The only exception is Campaign 1 (SHU_6 (Educators and Principals)), which reached 8,761 people aged 35–44, but it makes sense due to the target audience. However, Campaign 10 (SHU_Students (UK)), Campaign 4 (SHU_Students (Canada)), and Campaign 3(SHU_Students (Australia)) have low reach across all age groups, which is an important insight.

Pie/Donught Charts

Lets visualise how Reach is distributed across the different Age groups using a donught

donut <- campaign_age |>
  select(Age, Reach)

donut <- donut |>
  group_by(Age)|>
  summarise(Reach = sum(Reach))

donut <- donut |>
  arrange(desc(Age)) |>
  mutate(percent = Reach/sum(Reach)*100,
         label = if_else(percent > 4, paste0(round(percent,1),"%"),""),
         ypos = cumsum(Reach)- 0.5 * Reach )
donut |>
  ggplot()+
  geom_col(aes(x=2, y=Reach, fill = Age), width = 1)+
  geom_text(aes(x=2,y = ypos, label = label), family = "Century Gothic", fontface = "bold", colour = "white",size = 7 )+
  coord_polar(theta = "y")+
  xlim(0.5, 2.5)+
  scale_fill_manual(values = c("13-17"="#206095",
                               "18-24"="#F66068",
                               "25-34"="#22D0B6",
                               "35-44"="#871A5B",
                               "45-54"="#F98B00",
                               "55-64"="#002060"
  ))+
  theme_void()+
  labs(fill = "Age Group", title = "Campaign Reach Across Age groups")+
  theme(legend.text = element_text(size = 12, family = "Century Gothic", face = "bold"),
        text = element_text(family = "Century Gothic", face = "bold"),
        plot.title = element_text(hjust = 0.5, size = 15))

Lets similarly visulaise the Audience (Geography) by the Reach

pie <- campaign_clean 
pie <-  pie |>
  ungroup() |>
  mutate(Reach = as.numeric(Reach))|>
  arrange(desc(Geography))|>
  mutate(percent = Reach/sum(Reach)*100,
         label = if_else(percent > 3, paste0(scales::number(percent,accuracy = 0.1),"%"),""),
         ypos = cumsum(Reach)- 0.5 * Reach)

pie |>
  ggplot()+
  geom_col(aes(x="",y = Reach, fill = Geography))+
  coord_polar(theta = "y")+
  geom_text(aes(x = "",y = ypos, label = label), family = "Century Gothic", size = 6.5, fontface = "bold",colour = "white")+
  theme_void()+
  labs(fill = "Geography", title = "Campaign Reach by Geographic Locations")+
  guides(fill = guide_legend(reverse = FALSE))+
  scale_fill_manual(values = c("Group 1"="#4B644B",
                                 "Group 2"="#7D96AF",
                                 "Australia"="#F98B00",
                                 "Canada"="#09979B",
                                 "Ghana"="#EA879C",
                                 "India"="#FDD835",
                                 "Nepal"="#0070C0",
                                 "Nigeria"="#AE2B47",
                                 "UAE"="#EBA07E",
                                 "UK"="#E06666",
                                 "USA"="#002060"))+
  theme(text = element_text(family = "Century Gothic", size = 15, face = "bold"),
        plot.title = element_text(hjust = 0.5, size = 15))

Group 1 (Australia, Canada, United Kingdom, Ghana, Nigeria, Pakistan, United States) had the highest reach (24.6%) compared to the other geographical locations followed by India with 16.9% of the total reach of all campaigns.

Stacked Bar Chart

Let’s visualize how CTR is split among Age Ranges within each campaign.

ctrage <- campaign_raw |>
  select(campaign_id, Age, Impressions, Clicks)

ctrage <- ctrage |>
  group_by(Age, campaign_id)|>
  summarise(Impressions = sum(Impressions),
            Clicks = sum(Clicks))|>
  mutate(cltr = Clicks/Impressions*100)


ctrage_wide <- ctrage |>
  select(-Clicks, -Impressions) |>
  pivot_wider(campaign_id,
              names_from = Age,
              values_from = cltr)

ctrage_long <- ctrage_wide |>
  pivot_longer(-c(campaign_id),
               names_to = "Age",
               values_to = "ctr_rate")


ctrage_long |>
  mutate(campaign_id = factor(campaign_id, levels = c("Campaign 8", "Campaign 11","Campaign 2","Campaign 4",
                                                      "Campaign 3","Campaign 1","Campaign 10","Campaign 5","Campaign 6","Campaign 9","Campaign 7")))|>
  ggplot(aes(x = campaign_id, y = ctr_rate, fill = Age))+
  geom_col(width = 0.8)+
  geom_text(aes(label = if_else(ctr_rate > 0 ,paste0(round(ctr_rate,1), "%"),"")),
            position = position_stack(vjust = 0.5), family = "Century Gothic", fontface = "bold", color = "white", size = 5)+
  scale_fill_manual(values = c("13-17"="#206095",
                               "18-24"="#F66068",
                               "25-34"="#22D0B6",
                               "35-44"="#871A5B",
                               "45-54"="#F98B00",
                               "55-64"="#002060"
  )) +
  labs(x="Campaign Name", title = "Click-Through Rate Among Age Group Across Campaigns",
       y = "Click Through Rate (CTR) ")+
  scale_y_continuous(expand = c(0,0))+
  scale_x_discrete(labels = scales::wrap_format(width = 5))+
  theme(text = element_text(family = "Century Gothic", face = "bold", size = 14),
        panel.background = element_blank(),
        panel.grid.major.y = element_line(colour = "gray",size = 0.25),
        axis.text.x = element_text(size = 12, colour = "black"),
        axis.text.y = element_text(size = 14, colour = "black"),
        plot.title = element_text(hjust = 0.5, size = 15))

Campaign 8 (SHU_Students (Nigeria)) has the highest click-through rate (CTR), with the 18-24 and 25-34 age groups making up the majority. Campaign 11 (SHU_Students (USA)) comes second, also dominated by the 18-24 age group. Campaign 2 (SHU_3 (Students Apart from India and US)) follows, with a 17.8% CTR, again heavily targeting younger age groups. Once again, Campaign 1(SHU 6 (Educators and Principals))cstands out as the only campaign with a significant share from older age groups like 35-44, 45-54, and 55-64. Campaigns 5, 10, 9, 6 & 7 (*SHU_Students (Ghana), UK, UAE, India, and Nepal) have very low overall CTR. These campaigns aren’t contributing meaningfully to the total engagement and may need to be reevaluated or redesigned. Based on the color distributions, we can highlight that younger audiences (13–34) are the primary contributors to reach across nearly all student campaigns. The educator campaign is well-segmented, showing success in reaching an older demographic. We can keep or expand these based on the company’s goals.

KPI Analysis Build up

Let’s compare Click-Through Rate (CTR) across different geographies

geo_ctr <-  campaign_clean

geo_ctr |>
  ggplot(aes(x = reorder(Geography,CTR), y = CTR))+
  geom_col(width = 0.9, fill = "#871A5B")+
  geom_text(aes(label = paste0(scales::number(CTR, accuracy = 0.1),"%"), vjust = -1),
            family="Century Gothic", fontface = "bold", size = 7)+
  scale_y_continuous(expand = c(0,0), 
                     labels = scales::number_format(accuracy = 1),
                     limits = nicelimits)+
  labs(x = NULL, y = "Click-Through Rate (%)", title = "Click-Through Rate Across Geographic locations")+
  theme(text = element_text(family = "Century Gothic", face = "bold", size = 14),
        panel.background = element_blank(),
        panel.grid.major.y = element_line(colour = "gray",size = 0.25),
        axis.text.x = element_text(size = 15, colour = "black"),
        axis.text.y = element_text(size = 15, colour = "black"),
        plot.title = element_text(hjust = 0.5, size = 15))

What we can infer from this chart is that Nigeria has the highest click-through rate (CTR) among all geographies. USA and Group 2(Australia, Canada, United Kingdom, Ghana, Niger, Nigeria, Nepal, Pakistan, Thailand, Taiwan) follow with a CTR of 6.1% and 5.6% respectively. Canada, India, Australia, Ghana and the UK contribute similar CTRs, each falling within the range of 3–4%. On the other hand UAE, Group 1(Australia, Canada, United Kingdom, Ghana, Nigeria, Pakistan, United States) and Nepal show lower CTRs, ranging from 2.2 - 2.8%.
Nigeria and USA appear to be the most cost-effective and efficient regions in terms of campaign engagement, achieving higher CTRs. These insights suggest that optimizing efforts in high CTR regions or reevaluating strategies in lower CTR geographies like UAE and Nepal might yield better results.

Let’s now shift our focus to assess the same distribution but using uCTR as the metric.

geo_uctr <- campaign_clean

geo_uctr |>
  ggplot(aes(x = reorder(campaign_id,uCTR), y = uCTR))+
  geom_col(width = 0.9, fill = "#09979B")+
  geom_text(aes(label = paste0(scales::number(uCTR, accuracy = 0.1),"%"), vjust = -1),
            family="Century Gothic", fontface = "bold", size = 6)+
  scale_y_continuous(expand = c(0,0), 
                     labels = scales::number_format(accuracy = 1),
                     limits = nicelimits)+
  labs(x = NULL, y = "Unique Click-Through Rate (%)", title = "Unique Click-Through Rate Across Geographic Locations")+
  theme(text = element_text(family = "Century Gothic", face = "bold", size = 14),
        panel.background = element_blank(),
        panel.grid.major.y = element_line(colour = "gray",size = 0.25),
        axis.text.x = element_text(size = 13, colour = "black"),
        axis.text.y = element_text(size = 14, colour = "black"),
        plot.title = element_text(hjust = 0.5, size = 15))

The unique Click-Through Rate (uCTR), calculated as (Unique Clicks / Reach) * 100, indicates the percentage of individuals who engaged with the campaign out of the total audience reached. A lower uCTR reflects weaker engagement. Campaign 9 (The SHU Students (UAE)) campaign recorded the lowest uCTR, suggesting it had the least effective audience engagement among all campaigns.

cpc_campaign <- campaign_clean

cpc_campaign |>
  ggplot(aes(x = reorder(campaign_id,CPC), y = CPC))+
  geom_col(width = 0.8, fill = "tan")+
  coord_flip(clip = "off")+
  geom_text(aes(label = paste0(round(CPC,2),"$")), hjust = -0.2,
            family= "Century Gothic", fontface = "bold", colour = "black", size = 6)+
  scale_y_continuous(expand = c(0,0),
                     limits = c(0,8.5))+
  scale_x_discrete(labels = scales::wrap_format(width = 20))+
  labs(x = NULL,
       y = "Cost Per Click ($)", title = "Cost Per Click Across Campaigns")+
  theme(text = element_text(family = "Century Gothic", face = "bold", colour = "black"),
        panel.background = element_blank(),
        panel.grid.major.x = element_line(colour = "gray",size = 0.25),
        axis.text.y = element_text(size = 14, colour = "black"),
        axis.text.x = element_text(size = 13, colour = "black"),
        axis.title = element_text(size = 14, colour = "black", vjust = -2),
        plot.title = element_text(hjust = 0.5, size = 15))

CPC is a metric that shows how much you pay for each click on your ad. It is calculated by dividing the total amount spent by the number of clicks received. CPC helps measure the efficiency of your ad spend. A lower CPC means you’re getting more clicks for less money, while a higher CPC may suggest your ad needs improvement.

ulc_amount <- campaign_clean

ulc_amount |>
  ggplot(aes(x = ULC, y = amount_spent, fill = campaign_id))+
  geom_point(size = 8, shape = 21) +
  scale_fill_manual(values = c("Campaign 1"="#4B644B",
                               "Campaign 2"="#7D96AF",
                               "Campaign 3"="#EBA07E",
                               "Campaign 4"="#09979B",
                               "Campaign 5"="#EA879C",
                               "Campaign 6"="#FDD835",
                               "Campaign 7"="#0070C0",
                               "Campaign 8"="#AE2B47",
                               "Campaign 9"="#FFFFCC",
                               "Campaign 10"="#E06666",
                               "Campaign 11"="#002060"))+
  labs(x = "Unique Link Clicks (ULC)",
       y = "Total Amount SPent ($)", title = "Unique Link Clicks vs Amount Spent on Campaign",
       fill = NULL)+
  scale_y_continuous(
    limits = c(500,3000))+
  theme(text = element_text(family = "Century Gothic", face = "bold", colour = "black", size = 16),
        panel.background = element_blank(),
        axis.line = element_line(colour = "gray", size = 0.5),
        axis.text= element_text(size = 18),
        legend.background = element_rect(colour = "black"),
        plot.title = element_text(hjust = 0.5, size = 15))

This metric highlights the cost-efficiency of each campaign by comparing total amount spent to the number of unique link clicks (ULC). Campaigns with high spend but relatively low ULC such as Campaign SHU 6 (Educators and Principals) indicate lower efficiency and may require strategic adjustments. In contrast, campaigns with high ULC and moderate spend reflect stronger performance and better return on investment.

ctr_reach <- campaign_clean 

ctr_reach <- ctr_reach |>
  mutate(vjust_pos = if_else(campaign_id == "Campaign 9",1.8,-0.9))

ctr_reach |>
  ggplot(aes(x = Reach, y = CTR, colour = campaign_id))+
  geom_point(size = 4)+
  geom_text(aes(label = str_wrap(campaign_id, width = 34), vjust = vjust_pos), 
            family = "Century Gothic", fontface = "bold", size = 4)+
  scale_y_continuous(expand = c(0,1))+
  scale_x_continuous(limits = nicelimits,
                     labels = scales::comma)+
  scale_colour_manual(values = c("Campaign 1"="#4B644B",
                                 "Campaign 2"="#7D96AF",
                                 "Campaign 3"="#EBA07E",
                                 "Campaign 4"="#09979B",
                                 "Campaign 5"="#EA879C",
                                 "Campaign 6"="#FDD835",
                                 "Campaign 7"="#0070C0",
                                 "Campaign 8"="#AE2B47",
                                 "Campaign 9"="#F98B00",
                                 "Campaign 10"="#871A5B",
                                 "Campaign 11"="#002060"))+
  
  labs(x = "Reach", y = "CTR(%)", title = "Click-Through Rate vs Reach")+
  theme(text = element_text(family = "Century Gothic", face = "bold"),
        panel.background = element_blank(),
        axis.line = element_line(colour = "grey", size = 0.5),
        axis.text = element_text(size = 15),
        axis.title = element_text(size = 15),
        legend.position = "none",
        plot.title = element_text(hjust = 0.5, size = 15))

Campaign 7 (SHU_Students(Nepal)) despite having more reach has the lowest CTR of all the campaigns.

cpc_ctr <- campaign_clean

cpc_ctr <- cpc_ctr|>
  mutate(vjust_pos = if_else(campaign_id == "Campaign 10",1.5,-0.6))

cpc_ctr |>
  ggplot(aes(x=CTR, y=CPC, colour = campaign_id))+
  geom_point(size = 4)+
  geom_text(aes(label = str_wrap(campaign_id, width = 40), vjust = vjust_pos), 
            family = "Century Gothic", fontface = "bold", size = 6)+
  scale_y_continuous(expand = c(0,1))+
  scale_x_continuous(limits = nicelimits,
                     labels = scales::comma)+
  scale_colour_manual(values = c("Campaign 1"="#4B644B",
                                 "Campaign 2"="#7D96AF",
                                 "Campaign 3"="#EBA07E",
                                 "Campaign 4"="#09979B",
                                 "Campaign 5"="#EA879C",
                                 "Campaign 6"="#FDD835",
                                 "Campaign 7"="#0070C0",
                                 "Campaign 8"="#AE2B47",
                                 "Campaign 9"="#F98B00",
                                 "Campaign 10"="#871A5B",
                                 "Campaign 11"="#002060"))+
  
  labs(x = "Click Through Rate(%)", y = "Cost Per Click($)", title = "Cost Per Click vs Click-Through Rate")+
  theme(text = element_text(family = "Century Gothic", face = "bold"),
        panel.background = element_blank(),
        axis.line = element_line(colour = "grey", size = 1),
        axis.text = element_text(size = 20),
        axis.title = element_text(size = 20),
        legend.position = "none",
        plot.title = element_text(hjust = 0.5, size = 15))

The most efficient campaigns with high CTR and Low CPC are Campaing 8 (SHU_Students (Nigeria)) and Campaign 2 (SHU3_ (Students Apart from India and US)). Both of them show a very high CTR, and extremely low CPC, around 1 INR. Thus, these campaigns are high-performing and cost-effective. Similarly, the least efficient campaigns, with low CTR and high CPC are Campaign 3 (SHU_Students (Australia)) and Campaign 10 (SHU_Students (UK)). These have low to moderate CTR and an extremely high CPC. These are costly and less engaging campaigns, and possibly not worth the investment.

Conclusion - KPI Initial Analysis

Based on a thorough performance analysis, dropping the SHU_Students (Australia) campaign is recommended. It has the highest Cost Per Click (CPC) at 7.15$ and its Click-Through Rate (CTR) is low at 3.3%. Given its high CPC and high spend (850$), the campaign may not be delivering proportional value and should be dropped.

Supporting Evidence:

  1. Highest Cost Per Click (CPC): At $7.15, Australia’s CPC is:
    • 608% higher than the average campaign CPC ($1.01)
    • 21x higher than the top-performing Nigeria campaign ($0.34)
    • Second-worst efficiency score in our multi-factor analysis
  2. Suboptimal Engagement:
    • 3.3% CTR (below the 4.2% campaign average)
    • Despite significant reach (14,492), conversion metrics remain poor
    • Underperforms compared to similar markets (UK, USA)
  3. Budget Impact:
    • Immediate cost savings: $850
    • Represents 14% of total marketing budget
    • High reallocation potential to better-performing campaigns
  4. Financial Impact:
    Discontinuing the Australia campaign will:
    • Eliminate the highest CPC in the portfolio
    • Free up \(850 for reallocation<br /> </li> <li>Improve overall campaign efficiency by approximately 18%<br /> </li> <li>Allow expansion of high-performing campaigns (Campaign 8 and 2)<br /> </li> <li>This is a test</li> </ul></li> </ol> </div> <div id="recommendation---kpi-initial-analysis" class="section level2"> <h2>Recommendation - KPI Initial Analysis</h2> <p><strong>Campaign 10</strong>, the <strong>SHU_Students (UK)</strong> has a high CPC of <strong>7.08\) and a slightly better CTR of 3.0%. While it’s not as inefficient as Campaign 3, it still lacks strong cost-effectiveness and should be considered for optimization trials.

      On the other hand, several campaigns clearly stand out and should definitely be retained:

      1. SHU_Students (Nigeria): With a CTR of 9.5% and CPC of just $0.34, it’s the top performer in both engagement and cost-efficiency.
      2. SHU3_ (Students Apart from India and US): Features an excellent balance with a CTR of 5.6% and a CPC of only $0.42, which means it is a very efficient spend.
      3. SHU_Students (USA): With a CTR of 6.1% and a moderate CPC of \(5.04</strong>, this campaign shows strong engagement despite a slightly higher cost.</li> </ol> </div> </div> <div id="kpi-analysis" class="section level1"> <h1>KPI Analysis</h1> <p>This analysis builds on the initial KPI analysis carried out previously <br></p> <p>Our analysis identified <strong>Campaign 3 [SHU_Students (Australia)]</strong> as the least effective campaign, with <strong>Campaign 10 [SHU_Students (UK)]</strong> as the second worst performer. While both underperform, we suggested optimizing the UK campaign due to its slightly better click-through rate (CTR).<br /> This section will support our recommendation by exploring the dataset and making visualizations to depict how the data validates our criteria. We will go through each of the supporting evidence listed in the previous section, and represent it visually.</p> <div class="sourceCode" id="cb18"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb18-1"><a href="#cb18-1" tabindex="-1"></a>high_cost <span class="ot">&lt;-</span> campaign_clean</span> <span id="cb18-2"><a href="#cb18-2" tabindex="-1"></a></span> <span id="cb18-3"><a href="#cb18-3" tabindex="-1"></a>high_cost <span class="ot">&lt;-</span> high_cost <span class="sc">|&gt;</span></span> <span id="cb18-4"><a href="#cb18-4" tabindex="-1"></a> <span class="fu">mutate</span>(<span class="at">highlight =</span> campaign_id <span class="sc">==</span> <span class="st">&quot;Campaign 3&quot;</span>) <span class="co">#highlight defined in libraries and functions section</span></span> <span id="cb18-5"><a href="#cb18-5" tabindex="-1"></a></span> <span id="cb18-6"><a href="#cb18-6" tabindex="-1"></a>high_cost<span class="sc">|&gt;</span></span> <span id="cb18-7"><a href="#cb18-7" tabindex="-1"></a> <span class="fu">ggplot</span>(<span class="fu">aes</span>(<span class="at">x =</span> <span class="fu">reorder</span>(campaign_id, CPC), <span class="at">y=</span> CPC, <span class="at">fill =</span> highlight))<span class="sc">+</span></span> <span id="cb18-8"><a href="#cb18-8" tabindex="-1"></a> <span class="fu">geom_col</span>(<span class="at">width =</span> <span class="fl">0.8</span>)<span class="sc">+</span></span> <span id="cb18-9"><a href="#cb18-9" tabindex="-1"></a> <span class="fu">coord_flip</span>(<span class="at">clip =</span> <span class="st">&quot;off&quot;</span>)<span class="sc">+</span></span> <span id="cb18-10"><a href="#cb18-10" tabindex="-1"></a> <span class="fu">geom_text</span>(<span class="fu">aes</span>(<span class="at">label =</span> <span class="fu">paste0</span>(<span class="fu">round</span>(CPC,<span class="dv">2</span>),<span class="st">&quot;\)")), hjust=-0.1, family = "Century Gothic", fontface = "bold",colour = "black", size = 5)+ scale_y_continuous(expand = c(0,0))+ scale_x_discrete(labels = scales::wrap_format(width = 20))+ scale_fill_manual(labels = c("FALSE"="Other", "TRUE"="Highest"), values = c("TRUE" = "#8B5E3C", "FALSE" = "#D2B48C"))+ guides(fill = guide_legend(reverse = TRUE))+ labs(x=NULL, fill = NULL, y = "Cost Per Click", title = "Cost Per Click Across Campaigns")+ theme(text = element_text(family = "Century Gothic", face = "bold", colour = "black"), panel.background = element_blank(), panel.grid.major.x = element_line(colour = "gray",size = 0.25), axis.text.y = element_text(size = 14, colour = "black"), axis.text.x = element_text(size = 12, colour = "black"), axis.title = element_text(size = 12, colour = "black", vjust = -2), legend.text = element_text(size = 12), plot.title = element_text(hjust = 0.5, size = 15))

The deeper shaded bar shows the campaign with the highest CPC out of all.
Key Insight: The Australian campaign, thus Campaign 3’s CPC is approximately 608% higher than the average campaign CPC, demonstrating severe cost inefficiency. Let’s visualize this;

avg <- campaign_clean 

Average_CPC = sum(campaign_clean$amount_spent)/sum(campaign_clean$Clicks)

avg_cpc <- tribble(~ind, ~value,
                   "Average Campaign CPC", Average_CPC,
                   "Campaign 3", as.numeric(campaign_clean[campaign_clean$campaign_id=="Campaign 3","CPC"]))

avg_cpc |>
  ggplot(aes(x = ind, y = value, fill = ind))+
  geom_col(width = 0.8)+
  geom_text(aes(label = paste0(scales::number(value, accuracy = 0.01),"$")),
            family = "Times New Roman", fontface = "bold", size = 6, vjust = -0.5)+
  geom_text(aes(label = ifelse(value > 5, paste0("608% higher ▲ \n"),"")), vjust = -0.1, 
            family = "Times New Roman", fontface = "bold",size = 6.5, colour = "#871A5B")+
  scale_y_continuous(expand = c(0,0), limits = c(0,8))+
  scale_fill_manual(values = c("Average Campaign CPC"= "#22D0B6",
                               "Campaign 3"= "#871A5B")) +
  labs(fill = NULL, x = NULL, y = "Cost Per Click", title = "Cost Per Click Comparison: Campaign Average vs Campaign 3")+
  theme(text = element_text(family = "Century Gothic", colour = "black", face = "bold", size = 13),
        axis.text = element_text(size = 18, colour = "black"),
        panel.background = element_blank(),
        panel.grid.major.y = element_line(size = 0.25, colour = "gray"),
        plot.title = element_text(hjust = 0.5),
        legend.position = "none")

This graph compares the average campaign CPC with Campaign 3 (Australian campaign)’s CPC. The difference is clearly visible in this graph. When benchmarked against the most cost-efficient campaign (Campaign 8) which had a CPC of 0.34\(, the contrast becomes even more dramatic:</p> <div class="sourceCode" id="cb20"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb20-1"><a href="#cb20-1" tabindex="-1"></a>avg_1 <span class="ot">&lt;-</span> campaign_clean</span> <span id="cb20-2"><a href="#cb20-2" tabindex="-1"></a></span> <span id="cb20-3"><a href="#cb20-3" tabindex="-1"></a>avg1_cpc <span class="ot">&lt;-</span> <span class="fu">tribble</span>(<span class="sc">~</span>ind, <span class="sc">~</span>value,</span> <span id="cb20-4"><a href="#cb20-4" tabindex="-1"></a> <span class="st">&quot;Campaign 3&quot;</span>, <span class="fu">as.numeric</span>(campaign_clean[campaign_clean<span class="sc">\)campaign_id =="Campaign 3" , "CPC"]), "Campaign 8", as.numeric(campaign_clean[campaign_clean\(</span>campaign_id <span class="sc">==</span><span class="st">&quot;Campaign 8&quot;</span>,<span class="st">&quot;CPC&quot;</span>]))</span> <span id="cb20-6"><a href="#cb20-6" tabindex="-1"></a>avg1_cpc <span class="sc">|&gt;</span></span> <span id="cb20-7"><a href="#cb20-7" tabindex="-1"></a> <span class="fu">ggplot</span>(<span class="fu">aes</span>(<span class="at">x =</span> <span class="fu">reorder</span>(ind,value), <span class="at">y =</span> value, <span class="at">fill =</span> ind))<span class="sc">+</span></span> <span id="cb20-8"><a href="#cb20-8" tabindex="-1"></a> <span class="fu">geom_col</span>(<span class="at">width =</span> <span class="fl">0.8</span>)<span class="sc">+</span></span> <span id="cb20-9"><a href="#cb20-9" tabindex="-1"></a> <span class="fu">geom_text</span>(<span class="fu">aes</span>(<span class="at">label =</span> <span class="fu">paste0</span>(scales<span class="sc">::</span><span class="fu">number</span>(value, <span class="at">accuracy =</span> <span class="fl">0.01</span>),<span class="st">&quot;\)")), family = "Times New Roman", fontface = "bold", size = 7, vjust = -0.5)+ geom_text(aes(label = ifelse(value > 5, paste0("23.3x higher ▲ "),"")), vjust = -0.1, family = "Times New Roman", fontface = "bold",size = 7, colour = "#871A5B")+ scale_y_continuous(expand = c(0,0), limits = c(0,8))+ scale_fill_manual(values = c("Campaign 8"= "#F98B00", "Campaign 3"= "#871A5B")) + labs(fill = NULL, x = NULL, y = "Cost Per Click", title = "Cost Per Click Comparison: Campaign 3 vs Campaign 8")+ theme(text = element_text(family = "Century Gothic", colour = "black", face = "bold", size = 13), axis.text = element_text(size = 18, colour = "black"), panel.background = element_blank(), panel.grid.major.y = element_line(size = 0.25, colour = "gray"), plot.title = element_text(hjust = 0.5), legend.position = "none")]), "Other Campaigns", sum(campaign_clean\(</span>amount_spent)<span class="sc">-</span><span class="fu">as.numeric</span>(campaign_clean[campaign_clean<span class="sc">\)campaign_id=="Campaign 3","amount_spent"])) reallocation |> ggplot(aes(x = name, y = value, fill = name))+ geom_col(width = 0.8)+ geom_text(aes(label = paste0("\(&quot;</span>, scales<span class="sc">::</span><span class="fu">comma</span>(value,<span class="at">accuracy =</span> <span class="fl">0.01</span>))), </span> <span id="cb27-9"><a href="#cb27-9" tabindex="-1"></a> <span class="at">family=</span><span class="st">&quot;Century Gothic&quot;</span>, <span class="at">fontface =</span> <span class="st">&quot;bold&quot;</span>, <span class="at">size =</span> <span class="dv">6</span>, <span class="at">vjust =</span> <span class="sc">-</span><span class="dv">1</span>)<span class="sc">+</span></span> <span id="cb27-10"><a href="#cb27-10" tabindex="-1"></a> <span class="fu">scale_y_continuous</span>(<span class="at">expand =</span> <span class="fu">c</span>(<span class="dv">0</span>,<span class="dv">0</span>), <span class="at">limits =</span> nicelimits)<span class="sc">+</span></span> <span id="cb27-11"><a href="#cb27-11" tabindex="-1"></a> <span class="fu">scale_fill_manual</span>(<span class="at">values =</span> <span class="fu">c</span>(<span class="st">&quot;Campaign 3&quot;</span><span class="ot">=</span><span class="st">&quot;#871A5B&quot;</span>,</span> <span id="cb27-12"><a href="#cb27-12" tabindex="-1"></a> <span class="st">&quot;Other Campaigns&quot;</span><span class="ot">=</span><span class="st">&quot;#04BCFC&quot;</span>))<span class="sc">+</span></span> <span id="cb27-13"><a href="#cb27-13" tabindex="-1"></a> <span class="fu">labs</span>(<span class="at">x=</span><span class="cn">NULL</span>, <span class="at">y=</span><span class="st">&quot;Budget (\))", title = "Reallocation Potential: Dropping Campaign 3")+ theme(text = element_text(family = "Century Gothic", face = "bold", size = 13), panel.background = element_blank(), panel.grid.major.y = element_line(size = 0.25, color = "gray"), axis.text.x = element_text(size = 15, colour = "black"), legend.position = "none", plot.title = element_text(hjust = 0.5))

Efficiency Projection

eff_projection <- campaign_clean

current_cpc <- sum(eff_projection$amount_spent)/sum(eff_projection$Clicks)

eff_projection_2 <- eff_projection |>
  filter(campaign_id != "Campaign 3")

improved_cpc <- sum(eff_projection_2$amount_spent)/sum(eff_projection_2$Clicks)

eff_proj <- tribble(~name,~value,
                    "Current Portfolio CPC", current_cpc,
                    "Improved Portfolio CPC", improved_cpc)

eff_proj |>
  ggplot(aes(x = name, y = value, fill = name))+
  geom_col(width = 0.8)+
  geom_segment(aes(x = "Current Portfolio CPC", xend = "Improved Portfolio CPC", y = current_cpc, yend = improved_cpc),
               arrow = arrow(length = unit(0.3, "cm")),
               size = 1,
               colour = "purple", linetype = "solid")+
  annotate("text", x= 1, y = 1, label = "6% Improvement", hjust = -1, family = "Century Gothic", fontface = "bold", colour = "purple", size = 4.5)+
  geom_text(aes(label = paste0("$", round(value,2)), vjust = -1),
            family = "Century Gothic", fontface = "bold", size = 6)+
  scale_fill_manual(values = c("Current Portfolio CPC"="lightblue", "Improved Portfolio CPC"="lightgreen"))+
  scale_y_continuous(expand = c(0,0), limits = nicelimits)+
  labs(x=NULL, y="Cost Per Click", title = "Overall Campaign Efficiency Improvement")+
  theme(panel.background = element_blank(),
        panel.grid.major.y = element_line(colour = "gray",size = 0.25),
        axis.text = element_text(colour = "black", size = 15, family = "Century Gothic", face = "bold"),
        axis.title = element_text(colour = "black", size = 15, family = "Century Gothic", face = "bold"),
        legend.position = "none",
        plot.title = element_text(colour = "black", size = 15, family = "Century Gothic", face = "bold", hjust = 0.5))

Recommendations

Dropping the Campaign 3 allows the client to focus their attention on more profitable campaigns. While the Campaign 10 is also a financial burden, we believe that it can be optimized. However, we see that Campaign 8, with a CTR of 9.5% and CPC of just \(0.34</strong>, comes up as the top performer in both engagement and cost-efficiency. Let’s visualize these metrics.</p> <div class="sourceCode" id="cb29"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb29-1"><a href="#cb29-1" tabindex="-1"></a><span class="co">#campaign performance 1: cpc vs ctr</span></span> <span id="cb29-2"><a href="#cb29-2" tabindex="-1"></a></span> <span id="cb29-3"><a href="#cb29-3" tabindex="-1"></a>cp1 <span class="ot">&lt;-</span> campaign_clean</span> <span id="cb29-4"><a href="#cb29-4" tabindex="-1"></a></span> <span id="cb29-5"><a href="#cb29-5" tabindex="-1"></a>cp1 <span class="ot">&lt;-</span> cp1 <span class="sc">|&gt;</span></span> <span id="cb29-6"><a href="#cb29-6" tabindex="-1"></a> <span class="fu">mutate</span>(<span class="at">highlight =</span> campaign_id <span class="sc">==</span> <span class="st">&quot;Campaign 8&quot;</span>)</span> <span id="cb29-7"><a href="#cb29-7" tabindex="-1"></a></span> <span id="cb29-8"><a href="#cb29-8" tabindex="-1"></a>cp1<span class="sc">|&gt;</span></span> <span id="cb29-9"><a href="#cb29-9" tabindex="-1"></a> <span class="fu">ggplot</span>(<span class="fu">aes</span>(<span class="at">x =</span> CTR, <span class="at">y =</span> CPC, <span class="at">colour =</span> highlight))<span class="sc">+</span></span> <span id="cb29-10"><a href="#cb29-10" tabindex="-1"></a> <span class="fu">geom_point</span>(<span class="at">size =</span> <span class="dv">4</span>)<span class="sc">+</span></span> <span id="cb29-11"><a href="#cb29-11" tabindex="-1"></a> <span class="fu">geom_text</span>(<span class="at">data =</span> <span class="fu">subset</span>(cp1, campaign_id <span class="sc">==</span> <span class="st">&quot;Campaign 8&quot;</span>),</span> <span id="cb29-12"><a href="#cb29-12" tabindex="-1"></a> <span class="fu">aes</span>(<span class="at">x =</span> CTR, <span class="at">y =</span> CPC), <span class="at">label =</span> <span class="st">&quot;Top Performer&quot;</span>, <span class="at">show.legend =</span> <span class="cn">FALSE</span>,</span> <span id="cb29-13"><a href="#cb29-13" tabindex="-1"></a> <span class="at">family =</span> <span class="st">&quot;Century Gothic&quot;</span>, <span class="at">vjust =</span> <span class="sc">-</span><span class="dv">1</span>, <span class="at">hjust =</span> <span class="fl">0.69</span>, <span class="at">size =</span> <span class="dv">5</span>, <span class="at">fontface =</span> <span class="st">&quot;bold&quot;</span>)<span class="sc">+</span></span> <span id="cb29-14"><a href="#cb29-14" tabindex="-1"></a> <span class="fu">scale_color_manual</span>(<span class="at">values =</span> <span class="fu">c</span>(<span class="st">&quot;FALSE&quot;</span><span class="ot">=</span><span class="st">&quot;grey&quot;</span>, <span class="st">&quot;TRUE&quot;</span><span class="ot">=</span> <span class="st">&quot;#E74C3C&quot;</span>),</span> <span id="cb29-15"><a href="#cb29-15" tabindex="-1"></a> <span class="at">labels =</span> <span class="fu">c</span>(<span class="st">&quot;FALSE&quot;</span><span class="ot">=</span><span class="st">&quot;Other Campaigns&quot;</span>, <span class="st">&quot;TRUE&quot;</span><span class="ot">=</span><span class="st">&quot;Campaign 8&quot;</span>))<span class="sc">+</span></span> <span id="cb29-16"><a href="#cb29-16" tabindex="-1"></a> <span class="fu">scale_x_continuous</span>(<span class="at">expand =</span> <span class="fu">c</span>(<span class="dv">0</span>,<span class="dv">0</span>),</span> <span id="cb29-17"><a href="#cb29-17" tabindex="-1"></a> <span class="at">limits =</span> nicelimits)<span class="sc">+</span></span> <span id="cb29-18"><a href="#cb29-18" tabindex="-1"></a> <span class="fu">scale_y_continuous</span>(<span class="at">expand =</span> <span class="fu">c</span>(<span class="dv">0</span>,<span class="dv">0</span>),</span> <span id="cb29-19"><a href="#cb29-19" tabindex="-1"></a> <span class="at">limits =</span> nicelimits)<span class="sc">+</span></span> <span id="cb29-20"><a href="#cb29-20" tabindex="-1"></a> <span class="fu">labs</span>(<span class="at">x =</span> <span class="st">&quot;Click Through Rate(%)&quot;</span>, <span class="at">y =</span> <span class="st">&quot;Cost Per Click(\))", title = "Campaign Performance: CTR vs CPC", colour = NULL)+ theme(text = element_text(family = "Century Gothic", face = "bold", size = 13), panel.background = element_blank(), axis.line = element_line(colour = "grey", size = 1), axis.text = element_text(size = 15), axis.title = element_text(size = 15), plot.title = element_text(hjust = 0.5, size = 15))

Campaign 8 stands ahead of all the other campaigns. However, the campaign with an excellent balance is Campaign 2 with a CTR of 5.6% and a CPC of only \(0.42</strong>. This implies it is a very efficient spend. Let’s also visualize this;</p> <div class="sourceCode" id="cb30"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb30-1"><a href="#cb30-1" tabindex="-1"></a><span class="co">#campaign performance 2: cpc vs ctr</span></span> <span id="cb30-2"><a href="#cb30-2" tabindex="-1"></a>cp2 <span class="ot">&lt;-</span> campaign_clean</span> <span id="cb30-3"><a href="#cb30-3" tabindex="-1"></a></span> <span id="cb30-4"><a href="#cb30-4" tabindex="-1"></a>cp2 <span class="ot">&lt;-</span> cp2 <span class="sc">|&gt;</span></span> <span id="cb30-5"><a href="#cb30-5" tabindex="-1"></a> <span class="fu">mutate</span>(<span class="at">highlight =</span> campaign_id <span class="sc">==</span> <span class="st">&quot;Campaign 2&quot;</span>)</span> <span id="cb30-6"><a href="#cb30-6" tabindex="-1"></a></span> <span id="cb30-7"><a href="#cb30-7" tabindex="-1"></a>cp2<span class="sc">|&gt;</span></span> <span id="cb30-8"><a href="#cb30-8" tabindex="-1"></a> <span class="fu">ggplot</span>(<span class="fu">aes</span>(<span class="at">x =</span> CTR, <span class="at">y =</span> CPC, <span class="at">colour =</span> highlight))<span class="sc">+</span></span> <span id="cb30-9"><a href="#cb30-9" tabindex="-1"></a> <span class="fu">geom_point</span>(<span class="at">size =</span> <span class="dv">4</span>)<span class="sc">+</span></span> <span id="cb30-10"><a href="#cb30-10" tabindex="-1"></a> <span class="fu">geom_text</span>(<span class="at">data =</span> <span class="fu">subset</span>(cp2, campaign_id <span class="sc">==</span> <span class="st">&quot;Campaign 2&quot;</span>),</span> <span id="cb30-11"><a href="#cb30-11" tabindex="-1"></a> <span class="fu">aes</span>(<span class="at">x =</span> CTR, <span class="at">y =</span> CPC), <span class="at">label =</span> <span class="st">&quot;Efficient Spend&quot;</span>, <span class="at">show.legend =</span> <span class="cn">FALSE</span>,</span> <span id="cb30-12"><a href="#cb30-12" tabindex="-1"></a> <span class="at">family =</span> <span class="st">&quot;Century Gothic&quot;</span>, <span class="at">vjust =</span> <span class="sc">-</span><span class="dv">1</span>, <span class="at">hjust =</span> <span class="fl">0.69</span>, <span class="at">size =</span> <span class="dv">5</span>, <span class="at">fontface =</span> <span class="st">&quot;bold&quot;</span>)<span class="sc">+</span></span> <span id="cb30-13"><a href="#cb30-13" tabindex="-1"></a> <span class="fu">scale_color_manual</span>(<span class="at">values =</span> <span class="fu">c</span>(<span class="st">&quot;FALSE&quot;</span><span class="ot">=</span><span class="st">&quot;grey&quot;</span>, <span class="st">&quot;TRUE&quot;</span><span class="ot">=</span> <span class="st">&quot;#22D0B6&quot;</span>),</span> <span id="cb30-14"><a href="#cb30-14" tabindex="-1"></a> <span class="at">labels =</span> <span class="fu">c</span>(<span class="st">&quot;FALSE&quot;</span><span class="ot">=</span><span class="st">&quot;Other Campaigns&quot;</span>, <span class="st">&quot;TRUE&quot;</span><span class="ot">=</span><span class="st">&quot;Campaign 2&quot;</span>))<span class="sc">+</span></span> <span id="cb30-15"><a href="#cb30-15" tabindex="-1"></a> <span class="fu">scale_x_continuous</span>(<span class="at">expand =</span> <span class="fu">c</span>(<span class="dv">0</span>,<span class="dv">0</span>),</span> <span id="cb30-16"><a href="#cb30-16" tabindex="-1"></a> <span class="at">limits =</span> nicelimits)<span class="sc">+</span></span> <span id="cb30-17"><a href="#cb30-17" tabindex="-1"></a> <span class="fu">scale_y_continuous</span>(<span class="at">expand =</span> <span class="fu">c</span>(<span class="dv">0</span>,<span class="dv">0</span>),</span> <span id="cb30-18"><a href="#cb30-18" tabindex="-1"></a> <span class="at">limits =</span> nicelimits)<span class="sc">+</span></span> <span id="cb30-19"><a href="#cb30-19" tabindex="-1"></a> <span class="fu">labs</span>(<span class="at">x =</span> <span class="st">&quot;Click Through Rate(%)&quot;</span>, <span class="at">y =</span> <span class="st">&quot;Cost Per Click(\))", title = "Campaign Performance: CTR vs CPC", colour = NULL)+ theme(text = element_text(family = "Century Gothic", face = "bold", size = 13), panel.background = element_blank(), axis.line = element_line(colour = "grey", size = 1), axis.text = element_text(size = 15), axis.title = element_text(size = 15), plot.title = element_text(hjust = 0.5, size = 15))

Campaign 2 sits perfectly in the lower middle part of the chart, which makes it not only an efficient spend, but ready for future optimizations. There’s another campaign that, despite having a larger CPC, has a higher CTR and, thus, stronger engagement. It’s Campaign 11, with a CTR of 6.1% and a moderate CPC of \(5.04</strong>. Strong engagement despite a slightly higher cost per click. Let’s visualize this;</p> <div class="sourceCode" id="cb31"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb31-1"><a href="#cb31-1" tabindex="-1"></a><span class="co">#campaign performance 3: cpc vs ctr</span></span> <span id="cb31-2"><a href="#cb31-2" tabindex="-1"></a>cp3 <span class="ot">&lt;-</span> campaign_clean</span> <span id="cb31-3"><a href="#cb31-3" tabindex="-1"></a></span> <span id="cb31-4"><a href="#cb31-4" tabindex="-1"></a>cp3 <span class="ot">&lt;-</span> cp3 <span class="sc">|&gt;</span></span> <span id="cb31-5"><a href="#cb31-5" tabindex="-1"></a> <span class="fu">mutate</span>(<span class="at">highlight =</span> campaign_id <span class="sc">==</span> <span class="st">&quot;Campaign 11&quot;</span>)</span> <span id="cb31-6"><a href="#cb31-6" tabindex="-1"></a></span> <span id="cb31-7"><a href="#cb31-7" tabindex="-1"></a>cp3<span class="sc">|&gt;</span></span> <span id="cb31-8"><a href="#cb31-8" tabindex="-1"></a> <span class="fu">ggplot</span>(<span class="fu">aes</span>(<span class="at">x =</span> CTR, <span class="at">y =</span> CPC, <span class="at">colour =</span> highlight))<span class="sc">+</span></span> <span id="cb31-9"><a href="#cb31-9" tabindex="-1"></a> <span class="fu">geom_point</span>(<span class="at">size =</span> <span class="dv">4</span>)<span class="sc">+</span></span> <span id="cb31-10"><a href="#cb31-10" tabindex="-1"></a> <span class="fu">geom_text</span>(<span class="at">data =</span> <span class="fu">subset</span>(cp2, campaign_id <span class="sc">==</span> <span class="st">&quot;Campaign 11&quot;</span>),</span> <span id="cb31-11"><a href="#cb31-11" tabindex="-1"></a> <span class="fu">aes</span>(<span class="at">x =</span> CTR, <span class="at">y =</span> CPC), <span class="at">label =</span> <span class="st">&quot;Strong Engagement&quot;</span>, <span class="at">show.legend =</span> <span class="cn">FALSE</span>,</span> <span id="cb31-12"><a href="#cb31-12" tabindex="-1"></a> <span class="at">family =</span> <span class="st">&quot;Century Gothic&quot;</span>, <span class="at">vjust =</span> <span class="sc">-</span><span class="dv">1</span>, <span class="at">hjust =</span> <span class="fl">0.69</span>, <span class="at">size =</span> <span class="dv">5</span>, <span class="at">fontface =</span> <span class="st">&quot;bold&quot;</span>, <span class="at">colour =</span><span class="st">&quot;blue&quot;</span>)<span class="sc">+</span></span> <span id="cb31-13"><a href="#cb31-13" tabindex="-1"></a> <span class="fu">scale_color_manual</span>(<span class="at">values =</span> <span class="fu">c</span>(<span class="st">&quot;FALSE&quot;</span><span class="ot">=</span><span class="st">&quot;grey&quot;</span>, <span class="st">&quot;TRUE&quot;</span><span class="ot">=</span> <span class="st">&quot;blue&quot;</span>),</span> <span id="cb31-14"><a href="#cb31-14" tabindex="-1"></a> <span class="at">labels =</span> <span class="fu">c</span>(<span class="st">&quot;FALSE&quot;</span><span class="ot">=</span><span class="st">&quot;Other Campaigns&quot;</span>, <span class="st">&quot;TRUE&quot;</span><span class="ot">=</span><span class="st">&quot;Campaign 11&quot;</span>))<span class="sc">+</span></span> <span id="cb31-15"><a href="#cb31-15" tabindex="-1"></a> <span class="fu">scale_x_continuous</span>(<span class="at">expand =</span> <span class="fu">c</span>(<span class="dv">0</span>,<span class="dv">0</span>),</span> <span id="cb31-16"><a href="#cb31-16" tabindex="-1"></a> <span class="at">limits =</span> nicelimits)<span class="sc">+</span></span> <span id="cb31-17"><a href="#cb31-17" tabindex="-1"></a> <span class="fu">scale_y_continuous</span>(<span class="at">expand =</span> <span class="fu">c</span>(<span class="dv">0</span>,<span class="dv">0</span>),</span> <span id="cb31-18"><a href="#cb31-18" tabindex="-1"></a> <span class="at">limits =</span> nicelimits)<span class="sc">+</span></span> <span id="cb31-19"><a href="#cb31-19" tabindex="-1"></a> <span class="fu">labs</span>(<span class="at">x =</span> <span class="st">&quot;Click Through Rate(%)&quot;</span>, <span class="at">y =</span> <span class="st">&quot;Cost Per Click(\))", title = "Campaign Performance: CTR vs CPC", colour = NULL)+ theme(text = element_text(family = "Century Gothic", face = "bold", size = 13), panel.background = element_blank(), axis.line = element_line(colour = "grey", size = 1), axis.text = element_text(size = 15), axis.title = element_text(size = 15), plot.title = element_text(hjust = 0.5, size = 15))